/*
 * Copyright (c) 2009-2011, okazoh_tk. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  - Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  - Neither the name of the copyright holder nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef ALKES_TFIXED_H_INCLUDED_
#define ALKES_TFIXED_H_INCLUDED_

#include "alkes/internal.h"

namespace alkes {

    namespace impl {

///@cond

template<typename T, int B, typename INT_TYPE, typename FRAC_TYPE>
struct FixedTrait
{
    typedef T value_type;
    typedef INT_TYPE int_type;
    typedef FRAC_TYPE frac_type;

    enum
    {
        FRAC_BITS   = B,
    };
};

typedef FixedTrait<int16_t,  8, int8_t,  uint8_t> Fixed16Trait;
typedef FixedTrait<int32_t, 16, int16_t, uint16_t> Fixed32Trait;
typedef FixedTrait<int64_t, 32, int32_t, uint32_t> Fixed64Trait;

///@endcond

    }

template<typename TRAIT>
struct TFixed
{
    typedef TRAIT Trait;
    typedef typename Trait::value_type      value_type;
    typedef typename Trait::int_type        int_type;
    typedef typename Trait::frac_type       frac_type;

    operator float() const { return to_float<float>(value_); }
    operator double() const { return to_float<double>(value_); }
    operator int8_t() const { return to_int<int8_t>(value_); }
    operator int16_t() const { return to_int<int16_t>(value_); }
    operator int32_t() const { return to_int<int32_t>(value_); }
    operator int64_t() const { return to_int<int64_t>(value_); }
    operator uint8_t() const { return to_int<uint8_t>(value_); }
    operator uint16_t() const { return to_int<uint16_t>(value_); }
    operator uint32_t() const { return to_int<uint32_t>(value_); }
    operator uint64_t() const { return to_int<uint64_t>(value_); }

    value_type integer() const { return static_cast<value_type>(value_ >> Trait::FRAC_BITS); }
    value_type frac() const { return value_ & (((value_type)1 << Trait::FRAC_BITS) - 1); }

    template<typename T>
    void set(T v) { from_int(v); }
    void set(float v) { value_ = from_float(v); }
    void set(double v) { value_ = from_float(v); }
    void set(const TFixed& v) { value_ = v.value_; }

    template<typename T>
    inline static value_type from_int(T v) { return (value_type)(v << Trait::FRAC_BITS); }

    template<typename T>
    inline static value_type from_float(T v) { return (value_type)(v * ((value_type)1 << Trait::FRAC_BITS)); }

    template<typename T>
    inline static value_type from(T v) { return from_int(v); }
    inline static value_type from(float v) { return from_float(v); }
    inline static value_type from(double v) { return from_float(v); }
    inline static value_type from(const TFixed& v) { return v.value_; }

    template<typename T>
    inline static T to_int(value_type v) { return (T)(v >> Trait::FRAC_BITS); }

    template<typename T>
    inline static T to_float(value_type v) { return (T)v / ((value_type)1 << Trait::FRAC_BITS); }

    template<class T>
    inline static TFixed fixed_cast(T v)
    {
        TFixed ret = {from_int(v)};
        return ret;
    }

    inline static TFixed fixed_cast(float v)
    {
        TFixed ret = {from_float(v)};
        return ret;
    }

    inline static TFixed fixed_cast(double v)
    {
        TFixed ret = {from_float(v)};
        return ret;
    }

    inline static TFixed fixed_cast(const TFixed& v)
    {
        return v;
    }

    value_type  value_;
};


template struct AL_EXPORT TFixed<impl::Fixed16Trait>;
template struct AL_EXPORT TFixed<impl::Fixed32Trait>;
template struct AL_EXPORT TFixed<impl::Fixed64Trait>;

typedef TFixed<impl::Fixed16Trait>    Fixed16;
typedef TFixed<impl::Fixed32Trait>    Fixed32;
typedef TFixed<impl::Fixed64Trait>    Fixed64;

#define fixed16_cast(v) alkes::Fixed16::fixed_cast(v)
#define fixed32_cast(v) alkes::Fixed32::fixed_cast(v)
#define fixed64_cast(v) alkes::Fixed64::fixed_cast(v)

}

template<typename T, typename U>
inline T fixed_cast(U v)
{
    return T::fixed_cast(v);
}


template<class T>
inline bool operator == (const alkes::TFixed<T>& lh, const alkes::TFixed<T>& rh)
{
    return lh.value_ == rh.value_;
}

template<class T>
inline bool operator != (const alkes::TFixed<T>& lh, const alkes::TFixed<T>& rh)
{
    return lh.value_ != rh.value_;
}

template<class T>
inline bool operator < (const alkes::TFixed<T>& lh, const alkes::TFixed<T>& rh)
{
    return lh.value_ < rh.value_;
}

template<class T>
inline bool operator <= (const alkes::TFixed<T>& lh, const alkes::TFixed<T>& rh)
{
    return lh.value_ <= rh.value_;
}

template<class T>
inline bool operator > (const alkes::TFixed<T>& lh, const alkes::TFixed<T>& rh)
{
    return lh.value_ > rh.value_;
}

template<class T>
inline bool operator >= (const alkes::TFixed<T>& lh, const alkes::TFixed<T>& rh)
{
    return lh.value_ >= rh.value_;
}


template<typename T, typename U>
inline alkes::TFixed<T> operator + (const alkes::TFixed<T>& lh, U rh)
{
    alkes::TFixed<T> tmp = {lh.value_ + alkes::TFixed<T>::from(rh)};
    return tmp;
}

template<typename T, typename U>
inline const alkes::TFixed<T>& operator += (alkes::TFixed<T>& lh, U rh)
{
    lh.value_ = static_cast<typename alkes::TFixed<T>::value_type>(lh.value_ + alkes::TFixed<T>::from(rh));
    return lh;
}

template<typename T>
inline float operator + (const alkes::TFixed<T>& lh, float rh)
{
    return static_cast<float>(lh) + rh;
}

template<typename T>
inline float operator + (float lh, const alkes::TFixed<T>& rh)
{
    return lh + static_cast<float>(rh);
}

template<typename T>
inline float& operator += (float& lh, const alkes::TFixed<T>& rh)
{
    lh += static_cast<float>(rh);
    return lh;
}

template<typename T>
inline double operator + (const alkes::TFixed<T>& lh, double rh)
{
    return static_cast<double>(lh) + rh;
}

template<typename T>
inline double operator + (double lh, const alkes::TFixed<T>& rh)
{
    return lh + static_cast<double>(rh);
}

template<typename T>
inline double& operator += (double& lh, const alkes::TFixed<T>& rh)
{
    lh += static_cast<double>(rh);
    return lh;
}


template<typename T, typename U>
inline alkes::TFixed<T> operator - (const alkes::TFixed<T>& lh, U rh)
{
    alkes::TFixed<T> tmp = {lh.value_ - alkes::TFixed<T>::from(rh)};
    return tmp;
}

template<typename T, typename U>
inline const alkes::TFixed<T>& operator -= (alkes::TFixed<T>& lh, U rh)
{
    lh.value_ = static_cast<typename alkes::TFixed<T>::value_type>(lh.value_ - alkes::TFixed<T>::from(rh));
    return lh;
}

template<typename T>
inline float operator - (const alkes::TFixed<T>& lh, float rh)
{
    return static_cast<float>(lh) - rh;
}

template<typename T>
inline float operator - (float lh, const alkes::TFixed<T>& rh)
{
    return lh - static_cast<float>(rh);
}

template<typename T>
inline float& operator -= (float& lh, const alkes::TFixed<T>& rh)
{
    lh -= static_cast<float>(rh);
    return lh;
}

template<typename T>
inline double operator - (const alkes::TFixed<T>& lh, double rh)
{
    return static_cast<double>(lh) - rh;
}

template<typename T>
inline double operator - (double lh, const alkes::TFixed<T>& rh)
{
    return lh - static_cast<double>(rh);
}

template<typename T>
inline double& operator -= (double& lh, const alkes::TFixed<T>& rh)
{
    lh -= static_cast<double>(rh);
    return lh;
}


template<typename T, typename U>
inline alkes::TFixed<T> operator * (const alkes::TFixed<T>& lh, U rh)
{
    alkes::TFixed<T> tmp = {lh.value_ * alkes::TFixed<T>::from(rh)};
    return tmp;
}

template<typename T, typename U>
inline const alkes::TFixed<T>& operator *= (alkes::TFixed<T>& lh, U rh)
{
    lh.value_ = static_cast<typename alkes::TFixed<T>::value_type>(lh.value_ * alkes::TFixed<T>::from(rh));
    return lh;
}

template<typename T>
inline float operator * (const alkes::TFixed<T>& lh, float rh)
{
    return static_cast<float>(lh) * rh;
}

template<typename T>
inline float operator * (float lh, const alkes::TFixed<T>& rh)
{
    return lh * static_cast<float>(rh);
}

template<typename T>
inline float& operator *= (float& lh, const alkes::TFixed<T>& rh)
{
    lh *= static_cast<float>(rh);
    return lh;
}

template<typename T>
inline double operator * (const alkes::TFixed<T>& lh, double rh)
{
    return static_cast<double>(lh) * rh;
}

template<typename T>
inline double operator * (double lh, const alkes::TFixed<T>& rh)
{
    return lh * static_cast<double>(rh);
}

template<typename T>
inline double& operator *= (double& lh, const alkes::TFixed<T>& rh)
{
    lh *= static_cast<double>(rh);
    return lh;
}

template<typename T>
inline alkes::TFixed<T> operator * (const alkes::TFixed<T>& lh, const alkes::TFixed<T>& rh)
{
    alkes::TFixed<T> tmp = {
        static_cast<typename alkes::TFixed<T>::value_type>(
            lh.integer() * rh.value_ + ((lh.frac() * rh.value_) >> alkes::TFixed<T>::Trait::FRAC_BITS))
    };
    return tmp;
}


template<typename T, typename U>
inline alkes::TFixed<T> operator / (const alkes::TFixed<T>& lh, U rh)
{
    alkes::TFixed<T> tmp = {lh.value_ / alkes::TFixed<T>::from(rh)};
    return tmp;
}

template<typename T, typename U>
inline const alkes::TFixed<T>& operator /= (alkes::TFixed<T>& lh, U rh)
{
    lh.value_ = static_cast<typename alkes::TFixed<T>::value_type>(lh.value_ / alkes::TFixed<T>::from(rh));
    return lh;
}

template<typename T>
inline float operator / (const alkes::TFixed<T>& lh, float rh)
{
    return static_cast<float>(lh) / rh;
}

template<typename T>
inline float operator / (float lh, const alkes::TFixed<T>& rh)
{
    return lh / static_cast<float>(rh);
}

template<typename T>
inline float& operator /= (float& lh, const alkes::TFixed<T>& rh) 
{
    lh /= static_cast<float>(rh);
    return lh;
}

template<typename T>
inline double operator / (const alkes::TFixed<T>& lh, double rh)
{
    return static_cast<double>(lh) / rh;
}

template<typename T>
inline double operator / (double lh, const alkes::TFixed<T>& rh)
{
    return lh / static_cast<double>(rh);
}

template<typename T>
inline double& operator /= (double& lh, const alkes::TFixed<T>& rh)
{
    lh /= static_cast<double>(rh);
    return lh;
}

template<typename T>
inline alkes::TFixed<T> operator / (const alkes::TFixed<T>& lh, const alkes::TFixed<T>& rh)
{
    alkes::TFixed<T> tmp = {
        static_cast<typename alkes::TFixed<T>::value_type>((lh.value_ / rh.value_) << alkes::TFixed<T>::Trait::FRAC_BITS)
    };
    return tmp;
}

#endif
